﻿using System;
using System.Diagnostics;

using Preactor.ObjectModel.Attributes;
using Preactor.ObjectModel.Entities;
using Preactor.ObjectModel.Interfaces;

namespace Preactor.ObjectModel.BaseTypes
{
    public class Operation : PreactorRecord, IOperation
    {
        #region Implementation of IOperation

        [PreactorField("MATERIAL CONTROL ORDER NO", FieldDeclarationType.FieldClassification)]
        public string OrderNumber { get { return (string)GetValue("OrderNumber"); } set { SetValue("OrderNumber", value); } }

        [PreactorField("MATERIAL CONTROL PART NO", FieldDeclarationType.FieldClassification)]
        public string PartNumber { get { return (string)GetValue("PartNumber"); } set { SetValue("PartNumber", value); } }

        [PreactorField("MATERIAL CONTROL OP NO", FieldDeclarationType.FieldClassification)]
        public int OperationNumber { get { return (int)GetValue("OperationNumber"); } set { SetValue("OperationNumber", value); } }

        [PreactorField("LAUNCH TIME", FieldDeclarationType.FieldClassification)]
        public DateTime SetupStartTime { get { return (DateTime)GetValue("SetupStartTime"); } set { SetValue("SetupStartTime", value); } }

        [PreactorField("START TIME", FieldDeclarationType.FieldClassification)]
        public DateTime StartTime { get { return (DateTime)GetValue("StartTime"); } set { SetValue("StartTime", value); } }

        [PreactorField("END TIME", FieldDeclarationType.FieldClassification)]
        public DateTime EndTime { get { return (DateTime)GetValue("EndTime"); } set { SetValue("EndTime", value); } }

        [PreactorField("JOBSRESOURCEFIELD", FieldDeclarationType.FieldClassification)]
        public string ResourceName { get { return (string)GetValue("ResourceName"); } set { SetValue("ResourceName", value); } }

        [PreactorField("JOBSRESOURCEFIELD", FieldDeclarationType.FieldClassification)]
        public int ResourceNameValue { get { return (int)GetValue("ResourceNameValue"); } set { SetValue("ResourceNameValue", value); } }

        [PreactorField("QUANTITY", FieldDeclarationType.FieldClassification)]
        public double Quantity { get { return (double)GetValue("Quantity"); } set { SetValue("Quantity", value); } }

        [PreactorField("LOT NUMBER", FieldDeclarationType.FieldClassification)]
        public int LotNumber { get { return (int)GetValue("LotNumber"); } set { SetValue("LotNumber", value); } }

        [PreactorField("SEQUENCE LOCK", FieldDeclarationType.FieldClassification)]
        public bool IsLocked { get { return (bool)GetValue("IsLocked"); } set { SetValue("IsLocked", value); } }

        [PreactorField("TOTAL PROCESS TIME", FieldDeclarationType.FieldClassification)]
        public TimeSpan TotalProcessTime { get { return (TimeSpan)GetValue("TotalProcessTime"); } set { SetValue("TotalProcessTime", value); } }

        [PreactorField("DISABLE OPERATION", FieldDeclarationType.FieldClassification)]
        public bool IsDisabled { get { return (bool)GetValue("IsDisabled"); } set { SetValue("IsDisabled", value); } }

        [PreactorField("DEMAND RANKING DATE", FieldDeclarationType.FieldClassification)]
        public DateTime EarliestStartDate { get { return (DateTime)GetValue("EarliestStartDate"); } set { SetValue("EarliestStartDate", value); } }

        [PreactorField("JOBSCUSTOMERFIELD", FieldDeclarationType.FieldClassification)]
        public string Customer { get { return (String)GetValue("Customer"); } set { SetValue("Customer", value); } }

        [PreactorField("PRIORITY", FieldDeclarationType.FieldClassification)]
        public int Priority { get { return (int)GetValue("Priority"); } set { SetValue("Priority", value); } }

        //[PreactorField("SETUP MULTIPLIER", FieldDeclarationType.FieldClassification)]
        //public int SetupMultiplier { get { return (int)GetValue("SetupMultiplier"); } set { SetValue("SetupMultiplier", value); } }
        
        [PreactorField("DELIVERY BUFFER", FieldDeclarationType.FieldClassification)]
        public TimeSpan DeliveryBuffer { get { return (TimeSpan)GetValue("DeliveryBuffer"); } set { SetValue("DeliveryBuffer", value); } }

        //[PreactorField("TOTAL SETUP TIME", FieldDeclarationType.FieldClassification)]
        //public TimeSpan TotalSetupTime { get { return (TimeSpan)GetValue("TotalSetupTime"); } set { SetValue("TotalSetupTime", value); } }

        [PreactorField("TIME PER BATCH", FieldDeclarationType.FieldClassification)]
        public bool IsTimePerBatch { get { return (bool)GetValue("IsTimePerBatch"); } set { SetValue("IsTimePerBatch", value); } }

        [PreactorField("PROCESS TIME", FieldDeclarationType.FieldClassification)]
        public TimeSpan ProcessTime { get { return (TimeSpan)GetValue("ProcessTime"); } set { SetValue("ProcessTime", value); } }

        [PreactorField("SEQ COMPLETED", FieldDeclarationType.FieldClassification)]
        public int MidBatchQuantity { get { return (int)GetValue("MidBatchQuantity"); } set { SetValue("MidBatchQuantity", value); } }

        [PreactorField("SEQ REFERENCE TIME", FieldDeclarationType.FieldClassification)]
        public DateTime MidBatchTime { get { return (DateTime)GetValue("MidBatchTime"); } set { SetValue("MidBatchTime", value); } }

        [PreactorField("SEQ EFFECTIVE PROCESS TIME", FieldDeclarationType.FieldClassification)]
        public TimeSpan EffectiveOperationTime { get { return (TimeSpan)GetValue("EffectiveOperationTime"); } set { SetValue("EffectiveOperationTime", value); } }

        [PreactorField("MAX LOT SIZE", FieldDeclarationType.FieldClassification)]
        public double LotSize { get { return (double) GetValue("LotSize"); } set { SetValue("LotSize", value); } }

        [PreactorField("AUTO SEQ RESTRICT", FieldDeclarationType.FieldClassification)]
        public bool IsSequencingEnabled { get { return (bool)GetValue("IsSequencingEnabled"); } set { SetValue("IsSequencingEnabled", value); } }

        [PreactorField("MATERIAL CONTROL COMPLETE", FieldDeclarationType.FieldClassification)]
        public bool IsMaterialComplete { get { return (bool)GetValue("IsMaterialComplete"); } set { SetValue("IsMaterialComplete", value); } }

        [PreactorField("SHORTAGES", FieldDeclarationType.FieldClassification)]
        public bool IsMaterialShortage { get { return (bool)GetValue("IsMaterialShortage"); } set { SetValue("IsMaterialShortage", value); } }

        [PreactorField("OVER SUPPLIED", FieldDeclarationType.FieldClassification)]
        public bool IsMaterialOverSupply { get { return (bool)GetValue("IsMaterialOverSupply"); } set { SetValue("IsMaterialOverSupply", value); } }

        [PreactorField("MATERIAL CONTROL UPDATE", FieldDeclarationType.FieldClassification)]
        public string DemandStatus { get { return (string)GetValue("DemandStatus"); } set { SetValue("DemandStatus", value); } }

        [PreactorField("SUPPLY DATE", FieldDeclarationType.FieldClassification)]
        public DateTime SupplyDate { get { return (DateTime)GetValue("SupplyDate"); } set { SetValue("SupplyDate", value); } }

        [PreactorField("DEMAND DATE", FieldDeclarationType.FieldClassification)]
        public DateTime DemandDate { get { return (DateTime)GetValue("DemandDate"); } set { SetValue("DemandDate", value); } }

        [PreactorField("EARLIEST START", FieldDeclarationType.FieldClassification)]
        public DateTime ActualEarliestStartDate { get { return (DateTime)GetValue("ActualEarliestStartDate"); } set { SetValue("ActualEarliestStartDate", value); } }

        [PreactorField("DUE DATE", FieldDeclarationType.FieldClassification)]
        public DateTime ActualDueDate { get { return (DateTime)GetValue("ActualDueDate"); } set { SetValue("ActualDueDate", value); } }

        public bool IsScheduled
        {
            // NOTE: PreactorObjectModel.PlanningBoard.IsOperationScheduled(RecordNumber); is twice as fast as ResourceNameValue != -1;
            // However we could make this even faster if we did not have to resolve the RecordNumber. I.e if IsOperationScheduled() method 
            // accpeted a primary key we could make this even faster. (nearly instantaneous.

            get 
            {
                if (PreactorObjectModel.PlanningBoard == null)
                {
                    return this.ResourceNameValue != -1;
                }
                else
                {
                    return PreactorObjectModel.PlanningBoard.IsOperationScheduled(RecordNumber);
                }
            }
        }

        public IResource Resource
        {
            get
            {
                return IsScheduled ? PreactorObjectModel.GetRecordByName<IResource>(ResourceName) : null;
            }
        }

        #endregion

        protected Operation(PrimaryKey id, PreactorObjectModel preactorObjectModel) : base(id, preactorObjectModel)
        {
        }

        public override string ToString()
        {
            return string.Format("Type: Operation, Format Name: {0}, PK: {1}", base.FormatName, base.Id.Value);
        }

        public event EventHandler<EventArgs> Scheduled;

        public event EventHandler<EventArgs> Unscheduled;

        /// <summary>
        /// Schedule the operation on the given resource taking into account TestOpOnResource Operation Times
        /// </summary>
        /// <param name="startTime"></param>
        /// <param name="resourceName"></param>
        /// <returns></returns>
        public bool Schedule(string resourceName, DateTime startTime)
        {
            IPlanningBoard pb = PreactorObjectModel.PlanningBoard;

            int resourceNumber = pb.GetResourceNumber(resourceName);

            var operationTimes = pb.TestOperationOnResource(RecordNumber, resourceNumber, startTime);

            if (operationTimes.HasValue)
            {
                if (pb.PutOperationOnResource(RecordNumber, resourceNumber, operationTimes.Value.ChangeStart))
                {
                    OnScheduled(new EventArgs());
                    return true;
                }

                return false;
            }

            return false;
        }

        public bool PutOnResource(string resourceName, DateTime dateTime)
        {
            IPlanningBoard pb = PreactorObjectModel.PlanningBoard;

            int resourceNumber = pb.GetResourceNumber(resourceName);

            // EvaluateProcessTime();

            PreactorObjectModel.Preactor.EvaluateRecord(FormatNumber, RecordNumber);

            if (pb.PutOperationOnResource(RecordNumber, resourceNumber, dateTime))
            {
                OnScheduled(new EventArgs());
                return true;
            }

            return false;

            // TODO: If false find out why. Look at secondary constraints, parent operation, operation duration etc...
        }

        /// <summary>
        /// Evaluate the process time of an operation before scheduling it.
        /// </summary>
        public void EvaluateProcessTime()
        {
            var processTimeFieldNumber = PreactorObjectModel.GetFormatFieldPair<IOperation>(x => x.ProcessTime).FieldNumber;
            PreactorObjectModel.Preactor.EvaluateRecord(FormatName, RecordNumber, processTimeFieldNumber); // This will ensure all the datetime fields have the correct values.
        }

        /// <summary>
        ///  If we have operation times we won't TestOperationOnResource. We will just QuickSchedule.
        /// </summary>
        /// <param name="resourceName"></param>
        /// <param name="operationTimes"></param>
        /// <returns></returns>
        public bool QuickSchedule(string resourceName, OperationTimes operationTimes)
        {
            IPlanningBoard pb = PreactorObjectModel.PlanningBoard;

            int resourceNumber = pb.GetResourceNumber(resourceName);

            // EvaluateProcessTime();
            PreactorObjectModel.Preactor.EvaluateRecord(FormatNumber, RecordNumber);

            // QuickPutOperationOnResource() seems to have a problem. Reverting to PutOp() for now
            // Need to use GetOperationTimes() with QuickPutOp() to get the correct times.
            if (pb.PutOperationOnResource(RecordNumber, resourceNumber, operationTimes.ChangeStart))
            {
                //var op = PreactorObjectModel.GetRecord<IOperation>( RecordNumber );

                //if (operationTimes.ChangeStart != op.SetupStartTime || operationTimes.ProcessStart != op.StartTime || operationTimes.ProcessEnd != op.EndTime)
                //{
                //    Debugger.Break();
                //}

                OnScheduled(new EventArgs());
                return true;
            }

            return false;

            // TODO: If false find out why. Look at secondary constraints, parent operation, operation duration etc...
        }

        public void Unschedule()
        {
            if (IsLocked)
            {
                return;
            }

            PreactorObjectModel.PlanningBoard.UnallocateOperation(RecordNumber, OperationSelection.ThisOperation);

            if (!IsScheduled)
            {
                OnUnscheduled(new EventArgs());
            }
        }

        protected virtual void OnScheduled(EventArgs e)
        {
            if (Scheduled != null)
            {
                Scheduled(this, e);
            }
        }

        protected virtual void OnUnscheduled(EventArgs e)
        {
            if (Unscheduled != null)
            {
                Unscheduled(this, e);
            }
        }
    }
}
